<?php
require_once 'phpICQ.Classes.php';
require_once 'WebIcqPro.class.php';

/**
 * Класс задачи
 * 
 * @author Alexey MAGician Sergeev
 * @version 1.0.0
 * @package phpICQ_Client
 * 
 */
class phpICQ_task 
{
    /**
     * current process identifier
     *
     * @var int
     */
    protected $pid;
    /**
     * parent process identifier
     *
     * @var in
     */
    protected $ppid;

    /**
     * Констуктор класса
     *
     */
    function __construct()
    {
    }

    /**
     * Запускает подчиненный (дочерный) процесс
     *
     */
    protected function fork()
    {
        $pid = pcntl_fork();
        if ($pid == -1)
            throw new Exception ('fork error on Task object');
        elseif ($pid) {
            # we are in parent class
            $this->pid = $pid;
            # echo "< in parent with pid {$his->pid}\n";
        } else{
            # we are is child
            $this->run();
        }
    }

    /**
     * Инициализирует приемник сигналов... какой-же кривой русский язык
     * Installs a signal handle
     * 
     */
    protected function signal($signal, $dispatchFrom, $handler)
    {
        //pcntl_signal(SIGTERM, array(&$this,"handleSignals"));
        pcntl_signal($signal, array($dispatchFrom, $handler));
    }
    
    protected function run()
    {
        $this->ppid = posix_getppid();
        $this->pid = posix_getpid();
    }

    /**
     * Вызывается когда задача завершается (из родителя)
     *
     */
    protected function finish()
    {
        echo "task finished {$this->pid}\n";
    }

    /**
     * Возвращает PID процесса
     *
     * @return int
     */
    protected function pid()
    {
        return $this->pid;
    }
} // class phpICQ_task


/**
 * Класс контроля агентов
 * 
 * @author Alexey MAGician Sergeev
 * @version 1.0.0
 * @package phpICQ_Client
 * 
 */
class phpICQ_agentControl extends phpICQ_task
{
    /**
     * @var phpICQ_taskManager
     */
    private $parentObject;
    /**
     * @var string
     */
    private $dsn;
    /**
     * @var phpICQ_baseConnection
     */
    private $baseConnection;
    /**
     * @var Log
     */
    private $logger;
    /**
     * @var int
     */
    private $sleep_time = 1;
    /**
     * @var array
     */
    protected $activeUsers;
    /**
     * @var bool
     */
    protected $serverIsRun;
    /**
     * @var bool
     */
    protected $agentControlIsReady;
    
    /**
     * Конструктор класса
     *
     * @param phpICQ_taskManager $parentObject
     * @param string $dsn
     */
    function __construct($parentObject, $dsn)
    {
        $this->parentObject        = $parentObject;
        $this->dsn                 = $dsn;
        $this->serverIsRun         = true;
        $this->agentControlIsReady = false;
    }

    private function createObjects() 
    {
        $this->baseConnection = new phpICQ_baseConnection($this->dsn, null, "AC");
        $this->logger         = $this->baseConnection->GetLoger('ac');
    }

    /**
     * Новые пользователи которые пришли в On-Line
     * 
     * @return mixed
     */
    protected function getNewActiveUsers() 
    {
        $sql = "SELECT `".phpICQ_SQLFieldsNames::usersprofiles_id_fieldname."` FROM `".
            phpICQ_SQLFieldsNames::usersprofiles_tablename."` WHERE `".phpICQ_SQLFieldsNames::usersprofiles_status_fieldname."` != '".
            phpICQ_statuses::STATUS_OFFLINE."'";
       
        if (count($this->activeUsers) != 0) {
            $sql.=" AND `".phpICQ_SQLFieldsNames::usersprofiles_id_fieldname."` NOT IN ('" . implode("','", $this->activeUsers) . "')";
        }
        
        $result = $this->baseConnection->query($sql);
        
        $newArr = array();
        if ($result->numRows() !== 0) {
            while (($row = $result->fetchRow()) == true) {
                $userID = $row[phpICQ_SQLFieldsNames::usersprofiles_id_fieldname];
                if (array_search($userID, $newArr) === false) { // На всякий случай.
                    $newArr[] = $userID;
                }
            }
        } else {
            return null;
        }
        
        var_dump($newArr);
        return $newArr;
    }
    
    /**
     * Запускает агента сборки сообщений
     */
    protected function run() 
    {
        parent::run();
        echo "runAgentControl\n";
        $this->createObjects();
        
        // Бум принимать сигналы (если повезет)...
        $this->signal(SIGTERM, $this, "sig_handler");
        $this->signal(SIGHUP,  $this, "sig_handler");
        $this->signal(SIGUSR1, $this, "sig_handler");
    
        $this->agentControlProcessFunction();
        
        //$this->parentObject->childFinished($this);
        
        // Убиваем все переменные
        $this->baseConnection->disconnect();
        unset($this->baseConnection);
        
        exit(0); // !!! Завершает этот процесс
    }

    /**
     * Получение всех необходимых статусов из базы
     *
     */
    private function agentControlProcessFunction()
    {
        while (phpICQ_settings::ServerStatusBase($this->baseConnection)) {
            $this->serverIsRun = true;
            $this->agentControlIsReady = true;
            
            sleep($this->sleep_time);
        }
        $this->serverIsRun = false;
    }

    /**
     * 
     */
    public function sig_handler($signo)
    {
        echo $signo."\n";
    }
    
    public function getRand()
    {
        return rand(1,100);
    }
    
} // phpICQ_agentControl

/**
 * Серверный агент для сбора и отправки сообщений
 * работает в нескольких потоках (сколько ICQ активно столько и потоков)
 * 
 * @author Alexey MAGician Sergeev
 * @version 1.0.0
 * @package phpICQ_Client
 * 
 */
class phpICQ_agent extends phpICQ_task 
{
    /**
     * Messages types
     */
    const MT_MESSAGE     = "message";
    const MT_USERSTATUS  = "useronline";
    const MT_CONTACTLIST = "contactlist";
    const MT_SHORTINFO   = "shortinfo";
    const MT_ACCEPTED    = "accepted";
    
    /**
     * @var phpICQ_taskManager
     */
    private $parentObject;
    /**
     * @var string
     */
    private $dsn;
	/**
     * @var WebIcqPro
     */
    private $icq;
    /**
     * @var int
     */
    private $userID;
    /**
     * @var phpICQ_userProfile
     */
    private $userProfile;
    /**
     * @var phpICQ_baseConnection
     */
    private $baseConnection;
    /**
     * @var Log
     */
    private $logger;
    /**
     * @var bool
     */
    private $agentIsRun;
    /**
     * @var bool
     */
    private $restartFlag;
    /**
     * @var int
     */
    private $sleep_time_onreconnect = 10;
    /**
     * @var int
     */
    private $sleep_time_onConnectionError = 20;

    /**
     * Конструктор класса агента сбора/отправки сообщений
     * 
     * @param phpICQ_taskManager $parentObject
     * @param int $userID
     * @param string $dsn
     */
    function __construct($parentObject, $userID, $dsn) 
    {
        $this->dsn          = $dsn;
        $this->userID       = $userID;
        $this->agentIsRun   = false;
        $this->restartFlag  = false;
        $this->parentObject = $parentObject;
        $this->userProfile  = null;
        
		/*$this->loger->log("Started");*/
    }
    
    /**
     * Создает новые экземпляры объектов
     *
     */
    private function createObjects() 
    {
        $this->baseConnection = new phpICQ_baseConnection($this->dsn, null, "Agent ".$this->userID);
        $this->userProfile    = new phpICQ_userProfile($this->userID, $this->baseConnection);
        $this->logger         = $this->baseConnection->GetLoger('bot_'.$this->userID);
        $this->icq            = new WebIcqPro( );
    }
    
    /**
     * Выводит сообщения для отладки на экран и в лог файл
     *
     * @param string $msg
     * @param bool $onScreenOnly
     */
    private function LogMSG($msg, $onScreenOnly = false) 
    {
        if ($this->userProfile == null) {
            echo "Agent userid=$this->userID: $msg\n";
        } else {
	        echo "Agent UIN-".$this->userProfile->GetUserProfileUIN().": ".$msg."\n";
	
	        if (!$onScreenOnly) {
	            $this->logger->log($msg);
	        }
        }
    }
    
    /**
     * Возвращает/устанавливает статус работы агента
     *
     * @param bool $value
     * @return bool
     */
    public function agentIsRun($value = null) 
    {
        $oldVal = $this->agentIsRun;
        if (($value !=null)&&(is_bool($value))) {
            $this->agentIsRun = $value;
            $this->LogMSG("agentIsRun set: ".$value);
        }
        return $oldVal;
    }
    
    /**
     * Устанавливает флаг перезапуска агента
     */
    public function setRestartFlag() 
    {
        $this->restartFlag = true;
        $this->LogMSG("Reconect flag is set.");
    }
    
    /**
     * @param int $signo
     */
    public function sig_handler($signo) 
    {
         switch ($signo) {
             case SIGTERM:
                 // handle shutdown tasks
                 $this->LogMSG("handle shutdown tasks");
                 $this->agentIsRun = false;
                 @$this->icq->disconnect();
                 exit;
                 break;
             case SIGHUP:
                 // handle restart tasks
                 $this->LogMSG("handle restart tasks");
                 $this->setRestartFlag();
                 break;
             case SIGUSR1:
                 $this->LogMSG("Caught SIGUSR1...");
                 break;
             default:
                 // handle all other signals
                 $this->LogMSG("Unknown signal: ".$signo);
         }        
    }

    /**
     * Запускает агента сборки сообщений
     */
    public function run() 
    {
        parent::run();
        $this->agentIsRun(true);
        $this->createObjects();
        
        $this->LogMSG("Run");

        // Бум принимать сигналы (если повезет)...
        $this->signal(SIGTERM, $this, "sig_handler");
        $this->signal(SIGHUP,  $this, "sig_handler");
        $this->signal(SIGUSR1, $this, "sig_handler");
    
        $this->agentProcessFunction();
        
        $this->parentObject->childFinished($this);

        // Убиваем все переменные
        unset($this->userProfile);
        
        $this->baseConnection->disconnect();
        unset($this->baseConnection);
        
        $this->userProfile = null;
        $this->LogMSG("Exit");
        exit(0); // !!! Завершает этот процесс
    }

    /**
     * Действие по умолчанию (выводим все что есть в лог)
     *
     * @param array $msg
     */
    private function actionDefault($msg)
    {
        if (is_array($msg)) {
            if (array_key_exists("type", $msg)) {
                echo "Type: ".$msg ['type']."\n";
            }
            $out = print_r($msg, true);
            $this->logger->err($out);
            echo $out;
        } else {
            //var_dump($msg);
        }
    }
    
    /**
     * Устанавливает новый статус у контакта
     *
     * @param array $msg
     */
    private function actionSetContactStatus($msg)
    {
    	$contact = new phpICQ_contact ($this->userProfile, $msg ['uin'] );
    	if (isset ( $msg ['status'] )) {
    		$contact->SetContactStatus ( $msg ['status'] );
    	} else {
    		$contact->SetContactStatus ( phpICQ_Statuses::STATUS_OFFLINE );
    	}
    }
    
    private function actionNewMessage($msg) 
    {
        $this->LogMSG("Get Massage!");
        $this->logger->info(print_r($msg, true));
        
        // Определим кодировку сообщения
        $MessageCharSet = null;
        if (isset ( $msg ['charset'] )) {
            if ($msg ['charset'] ['numset'] === 2) {
                $MessageCharSet = 'UTF-16';
            } elseif ($msg ['charset'] ['numset'] === 3) {
                $MessageCharSet = 'UTF-8';    
            } else { // Вероятнее всего пришло в оффлайне
                $MessageCharSet = $this->userProfile->outputCharSet();
            } 
        }
        
        $contact = new phpICQ_contact ( $this->userProfile, $msg ['from'] );
        $contactCharSet = $contact->GetContactCharset();
        // Берем из профиля пользователя если указано
        $inputCharset = ($contactCharSet == null) ? $MessageCharSet :  $contactCharSet; 
        $message = mb_convert_encoding ( $msg ['message'], $this->userProfile->baseCharset(), $inputCharset );
        
        $contactmess = $contact->GetContactMessages ();
        $contactmess->AddMessage ( $message );
    }

    /**
     * Заполняет контакт лист полученный с сервера
     *
     */
    private function actionContactList() 
    {
        $this->LogMSG("Get contact list!");
        
        $list = $this->icq->getContactList();
        $contacnames = array_keys($list);
        foreach ( $contacnames as $name ) {
            $icq_contact = $list [$name];
        
            $contact_name = (array_key_exists('name', $icq_contact)) ? $icq_contact ['name'] : "";
            $contact = new phpICQ_contact ($this->userProfile , $name );
            $contact->SetAutosave(false);
            $contact->SetContactName ( $contact_name );
            $contact->SetContactGroupID ( $icq_contact ['group'] );
            $contact->SetContactServerID ( $icq_contact ['id'] );
            $contact->SetContactInList(true);
            $contact->SaveContactInBase();
        }
    }
    
    /**
     * Устанавливает статус доставки сообщения
     *
     * @param array $msg
     */
    private function actionMessageAccepted($msg)
    {
        $this->LogMSG("Message accepted for UIN: ".$msg ['uin']);
        
        $MessageServerID = $msg ['id'];
        //$contactUIN      = $msg ['uin'];
        // Ищем сообщение по полю "Server ID"
        $this->userProfile->ConfirmMessageDelivery($MessageServerID, true);
    }

    private function actionShortInfo($msg)
    {
        $this->LogMSG("Get short info!");
        $m = print_r($msg, true);
        $this->logger->log($m);
        echo $m;
        /*$contactUIN = $msg ['']
        $contact = new phpICQ_contact ( $userprofile, $contactUIN );
        $contact->SetAutosave(false);

        //$icq->getShortInfo("83984906");                
        array(11) {
          ["size"]=> int(56)
          ["myuin"]=> int(449152300)
          ["type"]=> string(9) "shortinfo"
          ["id"]=> int(2)
          ["nick"]=> string(8) "MAGician"
          ["firstname"]=> string(6) "Alexey"
          ["lastname"]=> string(7) "Sergeev"
          ["email"]=> string(0) ""
          ["authorization"]=> int(0)
          ["unknown"]=> int(2)
          ["gender"]=> int(0)
        } */
    }
    
    /**
     * Отсылает сообщения
     *
     */
    private function actionSendNewMessages()
    {
        $messages = $this->userProfile->GetMessagesForSend ();
        foreach ( $messages as $message ) {
            $for  = $message [phpICQ_SQLFieldsNames::messages_contactuin_fieldname];
            $text = $message [phpICQ_SQLFieldsNames::messages_message_fieldname];
            $id   = $message [phpICQ_SQLFieldsNames::messages_id_fieldname];
            
            // Конвертируем выходную строку
            $textC = $text;
            $textC = @mb_convert_encoding($text, $this->userProfile->outputCharSet(), $this->userProfile->baseCharset());
            
            $result = $this->icq->sendMessage ( $for, $textC );
            if ($result !== false) { // Все ОК - отправлено!
                $this->userProfile->ConfirmMessageSend ( $id, $result ); // Присвоим Server ID этому сообщению
            }
        }
    }
    
    /**
     * Основная функция обработки
     */
    private function agentProcessFunction() 
    {
        // Основной цикл
        while ($this->agentIsRun) {
//        	$start = microtime ();
        	$msg = false;
        	
        	if ($this->restartFlag) {
        			$this->LogMSG("Flag for reconnect is set! Sleep for $this->sleep_time_onreconnect seconds and reconecting.");
        			@$this->icq->disconnect();
        			sleep($this->sleep_time_onreconnect);
        	}
        	
        	if (@!$this->icq->isConnected()) {
        		// Примим кардинальные меры...
        		unset($this->icq);
        		$this->icq = new WebIcqPro( );
        		$this->userProfile->usercontactlist->SetAllContactsOffLineStatus();
        		
        		$this->LogMSG("Try to connect.");
        		
        		if ($this->icq->connect ( $this->userProfile->GetUserProfileUIN(), $this->userProfile->GetUserProfileUINPass() )) {
        			$this->icq->setStatus ( $this->userProfile->GetUserProfileStatus (), 'STATUS_DCCONT', $this->userProfile->GetUserProfileStatusText());
        			$this->icq->activateOfflineMessages ();
        
        			$this->LogMSG("ICQ connected! All Ok!");
        			continue;			
        		} else {
        			$this->LogMSG("ICQ error: ".$this->icq->error."  (sleepeng for $this->sleep_time_onConnectionError seconds)");
        			$this->icq->error = "";
        			sleep($this->sleep_time_onConnectionError);
        			continue;
        		}
        	} else {
        		$msg = $this->icq->readMessage ();
        	}

        	if ((isset ( $msg ['type'] )) && ($msg !== false)) {
                switch ($msg ['type']) {
        			case self::MT_USERSTATUS: // Изменение статуса у юзера
        			    $this->actionSetContactStatus($msg);
        				break;
        			case self::MT_MESSAGE:
        			    $this->actionNewMessage($msg);
        			    break;
        			case self::MT_CONTACTLIST:
        			    $this->actionContactList();
        			    break;
        			case self::MT_ACCEPTED:
        			    $this->actionMessageAccepted($msg);
        			    break;
                    case self::MT_SHORTINFO:
                        $this->actionMessageAccepted($msg);
                        break;
        			default:
        			    $this->actionDefault($msg);
        				break;
                }
            } else {
                if ($msg !== false) {
                    $this->actionDefault($msg);
                }
            }  // if type is set
    
            // Отправка сообщений
            $this->actionSendNewMessages();  
            

            // Обновим информацию о профайле  
            $this->userProfile->RefreshProfileInformation ();
            if ($this->userProfile->IsStatusChanged ()) {
                $this->icq->setStatus($this->userProfile->GetUserProfileStatus(), 'STATUS_DCCONT', $this->userProfile->GetUserProfileStatusText ());
            }
            if ($this->userProfile->IsXStatusChanged ()) {
                $this->icq->setXStatus($this->userProfile->GetUserProfileXStatus () );
            }
    
            // Агент запущен пока у него любой online статус 
            if ($this->agentIsRun) {
                $this->agentIsRun = phpICQ_statuses::IsOnline($this->userProfile->GetUserProfileStatus());
            }
            
            // TODO после отладки убрать

		    $fileStr = @file_get_contents(".restart_icq");
		    if ($fileStr !== false) {
		        if (strpos($fileStr, "2") !== false) {
		            $this->LogMSG("Return by flag. Exit.");
		            return null;
		        }
		    }
            
            flush();
        } // while agent is run
        
        if (@$this->icq->isConnected()) {
            $this->icq->disconnect();
        } 
        
        unset($this->icq);
        return null;
    }
} // phpICQ_agent 


class phpICQ_taskManager extends phpICQ_agentControl // TODO над этим подумать, может стоит вынести в переменную...
{
    /**
     * @var array
     */
    private $pool;
    /**
     * @var phpICQ_baseConnection
     */
    private $dsn;
    /**
     * @var $Log
     */
    private $loger;
    /**
     * @var string
     */
    private $fileRestartFlag;

    /**
     * Конструктор класса менеджера задач
     * 
     * @param string $dsn
     * @param string $fileRestartFlag
     */
    public function __construct($dsn, $fileRestartFlag)
    {
        parent::__construct($this, $dsn);
        $this->dsn             = $dsn;
        $this->pool            = array();
        $this->fileRestartFlag = $fileRestartFlag;
    }
    
    /**
     * Добавляет новую задачу в пул
     *
     * @param phpICQ_agent $task
     * @param int $id
     */
    private function add_task($task, $id)
    {
        $this->pool[] = (object)array("task" => $task, "id" => $id);
        $this->activeUsers[] = $id;
        //$task->fork();
        //$task->pid();
    }

    /**
     * Запускает несколько процессов
     * отдельный процесс для каждой задачи
     *
     */
    public function runTaskManager() 
    {
        $this->fork(); // Запускаем процесс контроля агентов
       sleep(5);
/*        while (!$this->agentControlIsReady){
            //echo $this->agentControlIsReady."\n";
            var_dump($this->agentControlIsReady);
            var_dump($this->getRand());
            sleep(1);
        } */ // Подождем пока стартует
        
        echo "Mega cikl\n";
        
        // Мегацикл =)
        while ($this->serverIsRun) {

		    $fileStr = @file_get_contents($this->fileRestartFlag);
		    if ($fileStr !== false) {
		        if (strpos($fileStr, "1") !== false) {
                    file_put_contents($this->fileRestartFlag, "0");
                    echo "Flag for reconnect is set!\n";

                    //Сообщим всем что пора реконектиться... 
                    foreach($this->pool as $taskObject) {
                    $taskObject->task->setRestartFlag();
                }
                
                sleep(10); // Раньше сысле нет, все спят =)
                continue;
		        }
		    }

            // Тут основнная деятельность... (создание новых дочерних процесов)
            echo "getNewActiveUsers\n";
            $act = $this->getNewActiveUsers();
            if ($act != null) { // Есть юзеры для которых агент еще не запущен
                
                foreach ($act as $id) {
                    $newAgent = new phpICQ_agent($this, $id, $this->dsn);
                    $this->add_task($newAgent, $id); 
                }

                // Завпустим все новые дочерние процессы
                for ($i=0; $i<count($act); $i++) {
                    $id = $act[$i];
                    $task = $this->id_to_task($id)->task;
                    $task->fork();
                }
            }
            
            echo "TM\n";
            sleep(2); //Передых....
        }
        
        echo "Main conplite. End all tasks...\n";

        foreach($this->pool as $taskObject) {
            $taskObject->task->agentIsRun(false);
        }
        
        // тут надо-бы завершить все процессы
        while (true) { // Ждем завершения всех задач
            
            $extra = null;
            $pid   = pcntl_wait($extra);
            if($pid == -1) {
                break; 
            } 
        }

        echo "\n\n  *** processes done! Exiting ***\n";
        exit(0);
    }
    
    /**
     * Вызывается когда дочерняя задача завершается
     *
     */
    public function childFinished($task)
    {
        $pid = $task->pid();
        echo " * task done: $pid\n";
        $this->finish_task($pid);
    }
    
    /**
     * Завершение задачи
     *
     * @param int $pid
     */
    private function finish_task($pid)
    {
        $taskObject = $this->pid_to_task($pid);
        if ($taskObject !== false) {
            $taskObject->task->finish();
            
            // Удалим его из активных пользователей 
            $key = array_search($taskObject->id, $this->activeUsers);
            if ($key !== false) {
                unset($this->activeUsers[$key]);
            }
        }
    }

    /**
     * Возвращает задачу по ее pid'у
     *
     * @param int $pid
     * @return mixed
     */
    private function pid_to_task($pid)
    {
        foreach($this->pool as $taskObject) {
            if($taskObject->task->pid() == $pid) {
                return $taskObject;
            }
        }
        return false;
    }
    
    /**
     * Возвращает задачу по ее id
     *
     * @param int $id
     * @return mixed
     */
    private function id_to_task($id)
    {
        foreach($this->pool as $taskObject) {
            if($taskObject->id == $id) {
                return $taskObject;
            }
        }
        return false;
    }
    
}

?>
